﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace Misuzilla.Crigate.Configuration
{
    public class ConfigurationManager
    {
        #region Configuration

        private XmlSerializer _xmlSerializer;
        private List<Type> _configurationTypes;

        private static Object _syncRoot = new Object();
        private static ConfigurationManager _configurationManagerDomain;

        private ConfigurationManager()
        {
            _configurationTypes = new List<Type>();
        }

        /// <summary>
        /// 設定情報管理クラスのインスタンスを取得します。
        /// </summary>
        /// <param name="session"></param>
        /// <returns></returns>
        public static ConfigurationManager GetConfigurationManager(Session session)
        {
            ConfigurationManager configurationManager;
            if (AppDomain.CurrentDomain.IsDefaultAppDomain())
            {
                if (session.ConfigurationManager == null)
                {
                    session.ConfigurationManager = new ConfigurationManager();

                }
                configurationManager = session.ConfigurationManager;
            }
            else
            {
                lock (_syncRoot)
                {
                    if (_configurationManagerDomain == null)
                    {
                        _configurationManagerDomain = new ConfigurationManager();
                    }
                    configurationManager = _configurationManagerDomain;
                }
            }

            configurationManager.UserConfigDirectory = session.UserConfigDirectory;
            return configurationManager;
        }

        #region Private Implementation
        /// <summary>
        /// 
        /// </summary>
        private String UserConfigDirectory
        {
            get; set;
        }
        
        /// <summary>
        /// 設定が登録されているかどうかを返します。
        /// </summary>
        /// <param name="configType"></param>
        /// <returns></returns>
        private Boolean IsConfigurationTypeRegistered(Type configType)
        {
            if (!_configurationTypes.Contains(configType))
            {
                // もう一度集め直してみる
                PrepareXmlSerializer();

                return _configurationTypes.Contains(configType);
            }
            
            return true;
        }

        /// <summary>
        /// XMLシリアライザを準備する
        /// </summary>
        private void PrepareXmlSerializer()
        {
            _configurationTypes = new List<Type>();

            // アセンブリの中から探す
            foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
            {
                Type configurationType = typeof(IConfiguration);
                foreach (Type t in asm.GetTypes())
                {
                    if (configurationType.IsAssignableFrom(t) && !t.IsAbstract && t.IsClass)
                    {
                        // IConfiguration
                        _configurationTypes.Add(t);
                    }
                }
            }

            // XMLのシリアライザの中で名前がかぶらないようにする
            XmlAttributeOverrides xmlAttrOverrides = new XmlAttributeOverrides();
            foreach (var configType in _configurationTypes)
            {
                XmlAttributes xmlAttributes = new XmlAttributes();
                xmlAttributes.XmlType = new XmlTypeAttribute(configType.FullName);
                xmlAttrOverrides.Add(configType, xmlAttributes);
            }
            _xmlSerializer = new XmlSerializer(typeof(Object), xmlAttrOverrides, _configurationTypes.ToArray(), null, null);

        }
        #endregion

        /// <summary>
        /// 指定した型を設定ファイルから読み込みます
        /// </summary>
        /// <param name="configType">設定の型</param>
        /// <returns>設定のインスタンス</returns>
        public Object GetConfig(Type configType)
        {
            if (!IsConfigurationTypeRegistered(configType))
                throw new ArgumentException("指定されたオブジェクトは設定ファイルとして登録されていない型です。");
            
            String fileName = configType.FullName + ".xml";
            String path = Path.Combine(Path.Combine(UserConfigDirectory, "Handlers"), fileName);

            if (File.Exists(path))
            {
                Trace.WriteLine(String.Format("Load Configuration: {0}", path));
                try
                {
                    using (FileStream fs = new FileStream(path, FileMode.Open))
                    {
                        try
                        {
                            Object retVal = _xmlSerializer.Deserialize(fs);
                            if (retVal != null)
                                return retVal;
                        }
                        catch (XmlException xe)
                        {
                            Trace.WriteLine(xe.Message);
                        }
                        catch (InvalidOperationException ioe)
                        {
                            Trace.WriteLine(ioe.Message);
                        }
                    }
                }
                catch (IOException ie)
                {
                    Trace.WriteLine(ie.Message);
                    throw;
                }
            }
            return null;
        }

        /// <summary>
        /// 指定した型を設定ファイルから読み込みます
        /// </summary>
        /// <returns>指定した設定型のインスタンス</returns>
        public T GetConfig<T>() where T : class, IConfiguration, new()
        {
            T retVal = GetConfig(typeof(T)) as T;
            return retVal ?? new T();
        }

        /// <summary>
        /// オブジェクトを設定ファイルに書き込みます
        /// </summary>
        /// <param name="o">保存する設定のインスタンス</param>
        public void SaveConfig(IConfiguration o)
        {
            Type configType = o.GetType();

            if (!IsConfigurationTypeRegistered(configType))
                throw new ArgumentException("指定されたオブジェクトは設定ファイルとして登録されていない型です。");

            String fileName = configType.FullName + ".xml";
            String path = Path.Combine(Path.Combine(UserConfigDirectory, "Handlers"), fileName);

            lock (o)
            {
                Trace.WriteLine(String.Format("Save Configuration: {0}", path));
                try
                {
                    String dir = Path.GetDirectoryName(path);
                    Directory.CreateDirectory(dir);
                    using (FileStream fs = new FileStream(path, FileMode.Create))
                    {
                        try
                        {
                            _xmlSerializer.Serialize(fs, o);
                        }
                        catch (XmlException xe)
                        {
                            Trace.WriteLine(xe.Message);
                            throw;
                        }
                        catch (InvalidOperationException ioe)
                        {
                            Trace.WriteLine(ioe.Message);
                            throw;
                        }
                    }
                }
                catch (IOException ie)
                {
                    Trace.WriteLine(ie.Message);
                    throw;
                }
            }
        }

        /// <summary>
        /// 指定した型の設定を初期化します。
        /// </summary>
        /// <typeparam name="T">初期化対象の設定型</typeparam>
        public void ResetConfig<T>() where T : class, IConfiguration, new()
        {
            T newObj = new T();
            SaveConfig(newObj);
        }
        #endregion

    }
}
